import time

import binance.error

from util.mysql_utils import *
from binance.websocket.um_futures.websocket_client import UMFuturesWebsocketClient
from binance.um_futures import UMFutures
import datetime
import json
from util.UTILS import *


mysql_utils = mysql_utils("localhost",
                          3306,
                          "root",
                          "joyingBox@123",
                          "himalaya")


# mysql_utils = mysql_utils("rm-wz99662u81d4b7d6sso.mysql.rds.aliyuncs.com",
#                           3306,
#                           "suib_tjb",
#                           "suib@2024",
#                           "himalaya")

# mysql_utils = mysql_utils("43.133.212.44",
#                           3306,
#                           "root",
#                           "joyingBox@123",
#                           "himalaya")

# logger.basicConfig(filename='/home/ubuntu/fundingrate.log', filemode='w', format='%(asctime)s  -- %(name)s - %(levelname)s - %(message)s', level=logger.INFO)


ORDER_STATUS_NEW = 'NEW'  #新订单 等待成交
ORDER_STATUS_PARTIALLY_FILLED = 'PARTIALLY_FILLED'  #部分成交
ORDER_STATUS_FILLED = 'FILLED'  #成交
ORDER_STATUS_CANCELED = 'CANCELED'  #取消订单
ORDER_STATUS_REJECTED = 'REJECTED'  #订单被拒
ORDER_STATUS_EXPIRED_IN_MATCH = 'EXPIRED_IN_MATCH'  #新订单 等待成绩

#已开仓
DEAL_RESULT_STATUS_OPENING = 0
#成交异常
DEAL_RESULT_STATUS_EXCEPTION = -1
#完成
DEAL_RESULT_STATUS_FINISH = 9

# 币安自动触发止损
DEAL_RESULT_REASON_BINA_AUTO_STOP_LOSS = 8

# 币安自动触发止盈
DEAL_RESULT_REASON_BINA_AUTO_STOP_PROFIT = 9
#开仓价格 30分钟  最低  触发 平仓
DEAL_RESULT__REASON_30m_min_low = 10
#上涨百分比 平仓
DEAL_RESULT__REASON_RISE_PREC = 11


# 币安止损 或 止盈单状态异常
DEAL_RESULT_REASON_BINA_SOTP_ORDER_ERROR = -10
# funding rate time  必须 与当前时间 在10秒以内
DEAL_RESULT_REASON_FUNDING_RATE_TIME_ERROR = -11
# 超时
DEAL_RESULT__REASON_ORDER_TIME_OUT = -12
#平仓异常
DEAL_RESULT__REASON_CLOSE_POSITION_ERROR = -13





class ClosePostion:

    def message_handler(self,_,message):
        if self.symbol:
            msg_obj = json.loads(message)
            if 'data' in msg_obj:
                self.agg_trade_list.append(msg_obj['data'])




    def subscribe_symbol(self,symbol):
        self.um_websocket_client.agg_trade(symbol=symbol)
        self.coin_list.add(symbol)

    def remove_coin_list_symbol(self,symbol):
        self.coin_list.remove(symbol)

    def un_subscribe_symbol(self,symbol):
        self.um_websocket_client.agg_trade(symbol=symbol,action=UMFuturesWebsocketClient.ACTION_UNSUBSCRIBE)
        self.coin_list.remove(symbol)

    def subscribe(self):
        for coin in self.coin_list:
            self.um_websocket_client.agg_trade(symbol=coin)

    def retry_connect(self):
        self.um_websocket_client = UMFuturesWebsocketClient(on_message=self.message_handler, is_combined=True,on_close=self.retry_connect)
        self.subscribe()

    def query_order(self,symbol,order_id):
        return self.um_futures_client.query_order(symbol,orderId=order_id)


    def cancel_order(self,symbol,order_id):
        try:
            return self.um_futures_client.cancel_order(symbol=symbol,orderId=order_id)
        except binance.error.ClientError as e:
            logger.error("cancel_order error :{}   {}  ......{}".format(symbol,order_id,e.error_message))
            return {'status':'error'}
    def market_new_order(self,open_position_data):
        order_side = open_position_data[18]
        #反正 side方向
        if order_side == 'BUY':
            order_side = 'SELL'
        elif order_side == 'SELL':
            order_side = 'BUY'
        else:
            return None

        return self.um_futures_client.new_order(symbol=open_position_data[2],type='MARKET',side=order_side,quantity=open_position_data[14])

    def __init__(self,open_position_data):

        self.himalaya_user_id = 3
        #获取交易所信息
        tl_exchange =  mysql_utils.query("select * from tl_exchange where status = 1 and user_id = {}".format(self.himalaya_user_id))[0]
        self.exchange_key = tl_exchange[2]
        self.exchange_secret_key = tl_exchange[3]
        self.um_futures_client = UMFutures(key=self.exchange_key , secret=self.exchange_secret_key)
        self.exchange_id = tl_exchange[0]
        #获取币对信息
        # tmp_coin_list = mysql_utils.query("select * from tl_exchange_coin where exchange_id = {}".format(self.exchange_id))
        self.coin_list = []
        self.agg_trade_list = []
        # for coin in tmp_coin_list:
        #     self.coin_list.append(coin[2])
        self.symbol = open_position_data[2]
        self.coin_list.append(self.symbol)
        self.um_websocket_client = UMFuturesWebsocketClient(on_message=self.message_handler, is_combined=True)
        self.subscribe()

        #订阅归集数据
        # self.subscribe()


    #检测 上涨百分比 fasle 不平仓  true 平仓
    def cal_30m_rise_perc(self,symbol,start_time,end_time,open_position_data):
        agg_trade_list = self.get_agg_trade()

        if len(agg_trade_list) == 0:
            return False

        # 策略 计算30分钟最低值
        last_time = start_time + (1000 * 60 * 30)
        open_price = open_position_data[5]

        last_agg_trade_time = agg_trade_list[len(agg_trade_list) - 1]['T']
        start_agg_trade_time = agg_trade_list[0]['T']
        trigger_condition = 0.04

        # 不触发 平仓
        cal_result = False
        #接茬结束时间
        if last_time > last_agg_trade_time:
            return cal_result
        #agg_trade 第一条时间  减40秒 如何还 大于 start_time 说明程序 延后 启动时间 大于 30秒了 这个是异常情况
        if start_time < start_agg_trade_time - (1000*40):
            return cal_result

        for agg_trade in agg_trade_list:
            price = float(agg_trade['p'])
            agg_trade_time = agg_trade['T']
            if agg_trade_time > last_agg_trade_time:
                break
            if (price/open_price-1) >= trigger_condition:
                try:
                    logger.info("@@### cal_30m_rise_perc agg_trade_price :{}   open_price:{}   agg_trade_time:{}".format(price,open_price,millisecond_2_date_str(agg_trade_time)))
                except:
                    logger.error('cal_30m_rise_perc  log error')
                return True

        return cal_result


    #计算  30分钟绝对离场信号 true 表示离场，false 表示不离场
    def cal_immediate_close_postion_30m_minlow(self,symbol,start_time,end_time,open_position_data):
        agg_trade_list = self.get_agg_trade()

        if len(agg_trade_list) == 0:
            return False

        open_price = open_position_data[5]
        #策略 计算45分钟最低值
        last_time = start_time + (1000*60*45)
        #归集 数据最后一条时间
        last_agg_trade_time = agg_trade_list[len(agg_trade_list) - 1]['T']
        start_agg_trade_time = agg_trade_list[0]['T']

        # 默认是
        cal_result = True
        #还没收集到足够的数据
        if last_time > last_agg_trade_time:
            return False

        # agg_trade 第一条时间  减40秒 如何还 大于 start_time 说明程序 延后 启动时间 大于 30秒了 这个是异常情况
        if start_time < start_agg_trade_time - (1000 * 40):
            return False

        check_time = 0
        for agg_trade in agg_trade_list:
            price = agg_trade['p']
            agg_trade_time = agg_trade['T']
            #如果 agg_trade 大于 lasttime 说明 超过 要检测的时间点
            if agg_trade_time < last_time:
                #失败  有一次open_price 大于 price  说明跌破了
                if open_price > float(price):
                    cal_result = False
                    break
                check_time += 1

        #一次都没对比过 可能是  没有agg_trade 数据 ，也有可能是   一次都没触发 agg_trade_time < last_time  因此返回 false 不影响 程序 执行
        if check_time == 0:
            return False
        return cal_result





    def close_position(self,open_position_data):
        try:
            #TODO 先检查仓位是否在
            new_order = self.market_new_order(open_position_data)

            if new_order == None:
                # TODO 下单异常 要如何处理 可以更新 数据库状态 标记为异常订单
                self.update_deal_result(open_position_data,DEAL_RESULT__REASON_CLOSE_POSITION_ERROR,DEAL_RESULT_STATUS_EXCEPTION)
                logger.info('deal_result_id  {}  下平仓单异常 ')

            if new_order:
                logger.info("close_position: deal_result_id:{}   close_position_order_id:{}".format(open_position_data[0],new_order['orderId']))
                time.sleep(0.2)
                new_order = self.query_order(open_position_data[2],new_order['orderId'])
            return new_order

        except Exception as e:
            # TODO 下单异常 要如何处理 可以更新 数据库状态 标记为异常订单
            logger.error(e)
            self.update_deal_result(open_position_data, DEAL_RESULT__REASON_CLOSE_POSITION_ERROR,
                                    DEAL_RESULT_STATUS_EXCEPTION)

            return None

    def update_deal_result(self,open_position_data,reason,result_status,close_price=None):
        update_sql = ''
        if close_price:
            open_price = open_position_data[5]
            amount = open_position_data[14]
            tmp_profit =  (open_price - float(close_price)) *amount


            update_sql = "update tl_deal_result set reason={},`status` = {},close_price={},close_time='{}',profit={} where id = {}".format(reason,result_status,close_price,millisecond_2_date_str(self.get_now()),tmp_profit,open_position_data[0])
        else:
            update_sql = "update tl_deal_result set reason={},`status` = {} where id = {}".format(reason,result_status,open_position_data[0])
        logger.info(update_sql)
        return mysql_utils.update(update_sql)
    def get_now(self):
        return datetime.now().timestamp() * 1000
    def get_agg_trade(self):
        return self.agg_trade_list


    #离场平仓
    def leave_close_postion(self,open_position_data,deal_reason):
        symbol = open_position_data[2]
        stop_profit_order_id = open_position_data[16]
        stop_loss_order_id = open_position_data[17]
        # 撤销止盈订单
        cancel_stop_profit_order = self.cancel_order(symbol, stop_profit_order_id)
        cancel_stop_loss_order = self.cancel_order(symbol, stop_loss_order_id)
        # TODO 撤销 失败 要如何处理
        if cancel_stop_profit_order == None or cancel_stop_profit_order['status'] != ORDER_STATUS_CANCELED:
            logger.info("撤销订单异常 ： {} deal_result_id  {}".format(stop_profit_order_id, open_position_data[0]))

        if cancel_stop_loss_order == None or cancel_stop_loss_order['status'] != ORDER_STATUS_CANCELED:
            print("撤销订单异常 ： {}  deal_result_id  {}".format(stop_loss_order_id, open_position_data[0]))

        # 直接 平仓
        close_position_order = self.close_position(open_position_data)
        if close_position_order != None:
            self.update_deal_result(open_position_data, deal_reason,
                                    DEAL_RESULT_STATUS_FINISH,close_position_order['avgPrice'])


    def check_funding_rate_time(self,open_position_data):
        funding_rate_time = open_position_data[4]
        # 检查 funding_time 是否 距离 10秒以内
        if abs(funding_rate_time - self.get_now()) > 10:

            return False
        else:
            return True
    def monitor(self,open_position_data):
        time.sleep(10)
        logger.info(" start monitor111")
        #监控 数据库 已开仓 未平仓
        # opening_data_list = mysql_utils.query("select * from tl_deal_result where status = 12")
        # for opening_data in opening_data_list:
        symbol = open_position_data[2]
        funding_rate = open_position_data[3]
        funding_time = open_position_data[4]
        # 检查 funding_time 是否 距离 10秒以内
        # if self.check_funding_rate_time(open_position_data) == False:
        #     self.update_deal_result(open_position_data, DEAL_RESULT_REASON_FUNDING_RATE_TIME_ERROR,
        #                             DEAL_RESULT_STATUS_EXCEPTION)
        #     return

        stop_profit_order_id = open_position_data[16]
        stop_loss_order_id = open_position_data[17]
        #成交最长等待时间为 4小时
        loop_stop_time = funding_time + ((1000*60*60*4)-(1000*60*10))
        m30_minlow = False
        while True:
            # 当 止盈 和 止损 都完成后 或者 打资金费率 开始 +4小时  就结束 循环

            stop_profit_order_status = False
            stop_loss_order_status = False
            stop_profit_order = self.query_order(symbol,stop_profit_order_id)
            stop_loss_order = self.query_order(symbol,stop_loss_order_id)
            #止盈止损 状态异常
            if stop_profit_order == None or stop_loss_order == None:
                self.update_deal_result(open_position_data,DEAL_RESULT_REASON_BINA_SOTP_ORDER_ERROR,DEAL_RESULT_STATUS_EXCEPTION)
                break


            if stop_profit_order['status'] == ORDER_STATUS_NEW:
                pass
            elif stop_profit_order['status'] == ORDER_STATUS_FILLED:
                stop_profit_order_status = True
                logger.info('{}  止盈成功 {}'.format(symbol,stop_profit_order_id))
            elif stop_profit_order['status'] == ORDER_STATUS_CANCELED:
                self.update_deal_result(open_position_data, DEAL_RESULT_REASON_BINA_SOTP_ORDER_ERROR,
                                        DEAL_RESULT_STATUS_EXCEPTION)
                break


            # if stop_profit_order_status:
            #     #止盈成功
            #     #取消止损单
            #     cancel_stop_loss_order = self.cancel_order(symbol, stop_loss_order_id)
            #     if cancel_stop_loss_order['status'] != ORDER_STATUS_CANCELED:
            #         logger.info("撤销订单异常 ： {}   deal_result_id  {}".format(stop_profit_order_id,open_position_data[0]))
            #         self.update_deal_result(open_position_data,DEAL_RESULT_REASON_BINA_SOTP_ORDER_ERROR,DEAL_RESULT_STATUS_EXCEPTION)
            #     #这里 需要更新平仓时间 和平常价格
            #     self.update_deal_result(open_position_data, DEAL_RESULT_REASON_BINA_AUTO_STOP_PROFIT,
            #                             DEAL_RESULT_STATUS_FINISH,stop_profit_order['avgPrice'])
            #     break



            if stop_loss_order['status'] == ORDER_STATUS_NEW:
                # print(1)
                pass
            #止损单 已成交
            elif stop_loss_order['status'] == ORDER_STATUS_FILLED:
                stop_loss_order_status = True
            elif stop_loss_order['status'] == ORDER_STATUS_CANCELED:
                self.update_deal_result(open_position_data, DEAL_RESULT_REASON_BINA_SOTP_ORDER_ERROR,
                                        DEAL_RESULT_STATUS_EXCEPTION)
                break




            if stop_loss_order_status == False and stop_profit_order_status == False:
                #检查 上涨百分比
                # rise_perc = self.cal_30m_rise_perc(symbol,funding_time,loop_stop_time,open_position_data)
                # if rise_perc:
                #     self.leave_close_postion(open_position_data, DEAL_RESULT__REASON_RISE_PREC)
                #     break

                m30_minlow = self.cal_immediate_close_postion_30m_minlow(symbol,funding_time,loop_stop_time,open_position_data)
                # 如果检查到时30分钟最低  止损 止盈单 撤单，仓位平仓
                if m30_minlow:
                    self.leave_close_postion(open_position_data,DEAL_RESULT__REASON_30m_min_low)
                    break
            else:
                if stop_loss_order_status:
                    # TODO 止损已经成交了 要怎么处理
                    cancel_stop_profit_order = self.cancel_order(symbol, stop_profit_order_id)
                    # TODO 撤销 失败 要如何处理
                    if cancel_stop_profit_order['status'] != ORDER_STATUS_CANCELED:
                        logger.info(
                            "撤销订单异常 ： {} deal_result_id  {}".format(stop_profit_order_id, open_position_data[0]))
                    self.update_deal_result(open_position_data, DEAL_RESULT_REASON_BINA_AUTO_STOP_LOSS,
                                            DEAL_RESULT_STATUS_FINISH,stop_loss_order['avgPrice'])
                if stop_profit_order_status:
                    # TODO 止盈已经成交了 取消止损订单
                    cancel_loss_profit_order = self.cancel_order(symbol, stop_loss_order_id)
                    # TODO 撤销 失败 要如何处理
                    if cancel_loss_profit_order['status'] != ORDER_STATUS_CANCELED:
                        logger.info(
                            "撤销订单异常 ： {} deal_result_id  {}".format(stop_loss_order_id, open_position_data[0]))
                    self.update_deal_result(open_position_data, DEAL_RESULT_REASON_BINA_AUTO_STOP_PROFIT,
                                            DEAL_RESULT_STATUS_FINISH,stop_profit_order['avgPrice'])



                break





            if self.get_now() >= loop_stop_time:
                #已经等待4小时了 直接平仓

                cancel_stop_profit_order = self.cancel_order(symbol, stop_profit_order_id)
                cancel_stop_loss_order = self.cancel_order(symbol, stop_loss_order_id)
                # TODO 撤销 失败 要如何处理
                if cancel_stop_profit_order == None or cancel_stop_profit_order['status'] != ORDER_STATUS_CANCELED:
                    print("撤销订单异常 ： {}  deal_result_id  {}".format(stop_profit_order_id,open_position_data[0]))
                if cancel_stop_loss_order == None or cancel_stop_loss_order['status'] != ORDER_STATUS_CANCELED:
                    print("撤销订单异常 ： {}  deal_result_id  {}".format(stop_loss_order_id,open_position_data[0]))

                # 直接 平仓
                close_position_order = self.close_position(open_position_data)
                if close_position_order != None:
                    self.update_deal_result(open_position_data,DEAL_RESULT__REASON_ORDER_TIME_OUT,DEAL_RESULT_STATUS_FINISH,close_position_order['avgPrice'])
                break
            time.sleep(2)



if __name__ == '__main__':
    opening_data_list = mysql_utils.query("select * from tl_deal_result where id = 43")
    cp = ClosePostion(opening_data_list[0])
    # print(11)
    cp.monitor(opening_data_list[0])
    # order = cp.query_order('LOOMUSDT','2215060568')
    # print(order)


    # print(datetime.datetime.now().timestamp() * 1000)
